//
//  STDSHotShaderEffect.hpp
//  STDRenderSDK
//
//  Created by 陈学明 on 2023/3/23.
//

#ifndef STDSHotShaderEffect_hpp
#define STDSHotShaderEffect_hpp

#include <stdio.h>
#include <iostream>
#include <fstream>
#include <sstream>
#include "STDSBaseEffect.hpp"
#include <rapidjson/document.h>
#include <rapidjson/istreamwrapper.h>

using namespace rapidjson;
using namespace std;
typedef enum {
    STDSUniformFloat,// 浮点型
    STDSUniformInt, // 整型
    STDSUniformTexture, // 纹理
    STDSUniformVec2, // 二维向量
    STDSUniformVec3, // 三位向量
    STDSUniformVec4, // 四维向量
    STDSUniformMat2, // 二阶矩阵
    STDSUniformMat3, // 三阶矩阵
    STDSUniformMat4, // 四阶矩阵
} STDSUniformType;

struct STDSHotShaderUniform {
    string name{""};
    string type{"f"};
    vector<int> iArr;
    vector<float> fArr;
    string texStr{""};
    string path{""};
    shared_ptr<STDSTexture> texture{nullptr};
    float min{0.0};
    float max{0.0};
    STDSUniformType uniformType{STDSUniformFloat};
    
    static STDSUniformType adpterUniformType(string &s) {
        if (s == "f") {
            return STDSUniformFloat;
        } else if (s == "i") {
            return STDSUniformInt;
        } else if (s == "texture") {
            return STDSUniformTexture;
        } else if (s == "vec2") {
            return STDSUniformVec2;
        } else if (s == "vec3") {
            return STDSUniformVec3;
        } else if (s == "vec4") {
            return STDSUniformVec4;
        } else if (s == "mat2") {
            return STDSUniformMat2;
        } else if (s == "mat3") {
            return STDSUniformMat3;
        } else if (s == "mat4") {
            return STDSUniformMat4;
        }
        return STDSUniformFloat;
    }
    
    void setParam(float *floatValue, int *intValue, const char *stringValue, int n) {
        if (uniformType == STDSUniformFloat) {
            fArr.clear();
            fArr.push_back(*floatValue);
        } else if (uniformType == STDSUniformInt) {
            iArr.clear();
            iArr.push_back(int(*intValue));
        } else if (uniformType == STDSUniformTexture) {
            path = string(stringValue);
        } else if (uniformType == STDSUniformVec2 ||
                   uniformType == STDSUniformVec3 ||
                   uniformType == STDSUniformVec4 ||
                   uniformType == STDSUniformMat2 ||
                   uniformType == STDSUniformMat3 ||
                   uniformType == STDSUniformMat4) {
            fArr.clear();
            float *p = floatValue;
            for (int i = 0; i < n; i++) {
                fArr.push_back(*p);
                p++;
            }
        }
    }
};

/// 热加载效果
/// 热加载效果文件结构
/// root-
///    ｜- paraments.json
///    ｜- fragment.glsl
///    ｜- vertex.glsl
///    ｜- images/
class STDSHotShaderEffect: public STDSBaseEffect {
public:
    STDSHotShaderEffect(string effect, string path) {
        mEffectName = effect;
        mRoot = path;
        mEffectType = STDSEffectTypeHotShader;
    }
    
    ~STDSHotShaderEffect() {
//        printf("dealloc--STDSHotShaderEffect\n");
    }
    
    void initGL() override {
        STDSBaseEffect::initGL();
        parseParaments();
    }
    
    void unInitGL() override {
        STDSBaseEffect::unInitGL();
        mHotProgram = nullptr;
    }
    
    string getEffectName() {
        return mEffectName;
    }
    
    void setFloatArguments(const char *name, float *floatValue, int n) override {
        for (auto &uniform: mUniforms) {
            if (strcmp(uniform->name.c_str(), name)== 0) {
                uniform->setParam(floatValue, nullptr, nullptr, n);
                return;
            }
        }
    }
    
    void setIntArtuments(const char *name, int *intValue, int n) override {
        for (auto &uniform: mUniforms) {
            if (strcmp(uniform->name.c_str(), name) == 0) {
                uniform->setParam(nullptr, intValue, nullptr, n);
                return;
            }
        }
    }
    
    void setStringArtuments(const char *name, const char *stringValue) override {
        for (auto &uniform: mUniforms) {
            if (strcmp(uniform->name.c_str(), name) == 0) {
                uniform->setParam(nullptr, nullptr, stringValue, 0);
                return;
            }
        }
    }
    
    void setTextureUniform(const char *name, shared_ptr<STDSTexture> tex) {
        for (auto &uniform: mUniforms) {
            if (strcmp(uniform->name.c_str(), name) == 0) {
                uniform->texture = tex;
                return;
            }
        }
    }
    
    void draw() override {
        if (mHotProgram == nullptr) {
            parseShader();
        }
        if (mHotProgram == nullptr) {
            return;
        }
        // 这里有个bug，需要提前准备好纹理
        for (auto &uniform: mUniforms) {
            if (uniform->uniformType == STDSUniformTexture) {
                getImageTexture(uniform);
            }
        }
        
        mHotProgram->use();
        int textureIndex = 0;
        for (auto &uniform: mUniforms) {
            if (uniform->uniformType == STDSUniformFloat) {
                mHotProgram->setUniform(mHotProgram->getUniform(uniform->name), uniform->fArr[0]);

            } else if (uniform->uniformType == STDSUniformInt) {
                mHotProgram->setUniform(mHotProgram->getUniform(uniform->name), uniform->iArr[0]);

            } else if (uniform->uniformType == STDSUniformVec2) {
                mHotProgram->setUniform2f(mHotProgram->getUniform(uniform->name), uniform->fArr[0], uniform->fArr[1]);

            } else if (uniform->uniformType == STDSUniformVec3) {
                mHotProgram->setUniform3f(mHotProgram->getUniform(uniform->name), uniform->fArr[0], uniform->fArr[1], uniform->fArr[2]);

            } else if (uniform->uniformType == STDSUniformVec4) {
                mHotProgram->setUniform4f(mHotProgram->getUniform(uniform->name), uniform->fArr[0], uniform->fArr[1], uniform->fArr[2], uniform->fArr[3]);

            } else if (uniform->uniformType == STDSUniformMat2) {
                mHotProgram->setMatrix2(mHotProgram->getUniform(uniform->name), uniform->fArr.data());

            } else if (uniform->uniformType == STDSUniformMat3) {
                mHotProgram->setMatrix3(mHotProgram->getUniform(uniform->name), uniform->fArr.data());

            } else if (uniform->uniformType == STDSUniformMat4) {
                mHotProgram->setMatrix(mHotProgram->getUniform(uniform->name), uniform->fArr.data());

            } else if (uniform->uniformType == STDSUniformTexture) {
                if (uniform->texture == nullptr) {
                    continue;
                }
                glActiveTexture(GL_TEXTURE0 + textureIndex);
                glBindTexture(GL_TEXTURE_2D, uniform->texture->getId());
                mHotProgram->setUniform(mHotProgram->getUniform(uniform->name), textureIndex);
                textureIndex +=1;
            }
        }
        if (mTexture != nullptr) {
            mHotProgram->setUniform2f(mHotProgram->getUniform("uSize"), mSizeSRC.x, mSizeSRC.y);
        }
        mAtbRect.setPosVex(mHotProgram->getAttrib("aPos"));
        mAtbRect.setPosTex(mHotProgram->getAttrib("aTexCoord"));
        mAtbRect.draw();
        mHotProgram->unuse();
    }
    
private:
    
    void getImageTexture(shared_ptr<STDSHotShaderUniform> uniform) {
        if (uniform == nullptr ||
            uniform->uniformType != STDSUniformTexture || uniform->texture != nullptr) {
            return;
        }
        if (uniform->texStr.length() > 0) {
            uniform->texture = mTexture;
        } else if (uniform->texture == nullptr && uniform->path.length() > 0) {
            auto path = mRoot + uniform->path;
            uniform->texture = mContext->getImageTexture(path);
        }
    }
    
    void parseParaments() {
        auto paramentPath = mRoot + "/paraments.json";
        ifstream ifs(paramentPath.c_str());
        IStreamWrapper isw(ifs);

        Document d;
        d.ParseStream(isw);
        if (d.IsArray()) {
            for (auto& v : d.GetArray()) {
                shared_ptr<STDSHotShaderUniform> uniform = make_shared<STDSHotShaderUniform>();
                if (!v.IsObject()) {
                    continue;
                }
                auto param = v.GetObject();
                auto type = string(param["type"].GetString());
                auto name = string(param["name"].GetString());
                auto min = param["min"].GetFloat();
                auto max = param["max"].GetFloat();
                uniform->type = type;
                uniform->name = name;
                uniform->min = min;
                uniform->max = max;
                uniform->uniformType = STDSHotShaderUniform::adpterUniformType(type);

                if (uniform->uniformType == STDSUniformFloat) {
                    auto value = param["value"].GetFloat();
                    uniform->fArr.push_back(value);
                } else if (uniform->uniformType == STDSUniformInt) {
                    auto value = param["value"].GetInt();
                    uniform->iArr.push_back(value);
                } else if (uniform->uniformType == STDSUniformTexture) {
                    auto value = param["value"].GetString();
                    auto path = param["path"].GetString();
                    uniform->path = path;
                    uniform->texStr = value;
                } else if (uniform->uniformType == STDSUniformVec2 ||
                           uniform->uniformType == STDSUniformVec3 ||
                           uniform->uniformType == STDSUniformVec4 ||
                           uniform->uniformType == STDSUniformMat2 ||
                           uniform->uniformType == STDSUniformMat3 ||
                           uniform->uniformType == STDSUniformMat4) {
                    if (param["value"].IsArray()) {
                        for (auto& fv : param["value"].GetArray()) {
                            auto fValue = fv.GetFloat();
                            uniform->fArr.push_back(fValue);
                        }
                    }
                }
                mUniforms.push_back(uniform);
            }
        }
    }
    
    void parseShader() {
        auto fragmentPath = mRoot + "/fragment.glsl";
        auto vertexPath = mRoot + "/vertex.glsl";
        std::string vertexCode;
        std::string fragmentCode;
        std::ifstream vShaderFile;
        std::ifstream fShaderFile;
        vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
        fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);
        try {
            vShaderFile.open(vertexPath.c_str());
            fShaderFile.open(fragmentPath.c_str());
            std::stringstream vShaderStream, fShaderStream;
            vShaderStream << vShaderFile.rdbuf();
            fShaderStream << fShaderFile.rdbuf();
            vShaderFile.close();
            fShaderFile.close();
            vertexCode   = vShaderStream.str();
            fragmentCode = fShaderStream.str();
        } catch (std::ifstream::failure& e) {
            std::cout << "ERROR::SHADER::FILE_NOT_SUCCESSFULLY_READ: " << e.what() << std::endl;
        }
        mHotProgram = make_shared<STDSShaderProgram>(vertexCode, fragmentCode);
        mHotProgram->init();
    }
    
private:
    shared_ptr<STDSShaderProgram> mHotProgram{nullptr};
    string mEffectName{""};
    string mRoot{""};
    vector<shared_ptr<STDSHotShaderUniform>> mUniforms;
};
#endif /* STDSHotDeploymentEffect_hpp */
